/*
 * Compiler.cpp
 *
 *  Created on: 1 Nov 2010
 *      Author: kn308
 */

#include <cstdio>
#include <iostream>
#include <stdexcept>
#include <csignal>
#include <fstream>

#include <MAlice/MAlice.h>
#include <MAlice/MAliceDriver.hpp>
#include <MAlice/Parser/MAliceParser.hpp>
#include <MAlice/Scanner/MAliceScanner.hpp>

#include <llvm/LLVMContext.h>
#include <llvm/Module.h>
#include <llvm/PassManager.h>
#include <llvm/Instructions.h>

#include <llvm/Support/raw_ostream.h>

#include <llvm/Analysis/Verifier.h>

#include <llvm/CodeGen/DwarfWriter.h>

#include <llvm/Bitcode/Archive.h>
#include <llvm/Bitcode/ReaderWriter.h>
#include <llvm/Bitcode/BitstreamReader.h>
#include <llvm/Bitcode/BitstreamWriter.h>

#include <llvm/Target/TargetData.h>
#include <llvm/Target/TargetSelect.h>

#include <llvm/Transforms/Scalar.h>
#include <llvm/Target/TargetSelect.h>
#include <llvm/TypeSymbolTable.h>

#include <llvm/Support/IRBuilder.h>


#include <MAlice/Ast/Ast.h>
#include <MAlice/Ast/ErrorAst.h>
#include <MAlice/Ast/ExpressionAst.h>
#include <MAlice/Ast/IntegerLiteralAst.h>
#include <MAlice/Ast/CharacterLiteralAst.h>
#include <MAlice/Ast/BinaryOperatorAst.h>
#include <MAlice/Ast/BinaryAddOperatorAst.h>
#include <MAlice/Ast/BinaryMulOperatorAst.h>
#include <MAlice/Ast/BinarySubOperatorAst.h>
#include <MAlice/Ast/BinaryDivOperatorAst.h>
#include <MAlice/Ast/BinaryModOperatorAst.h>
#include <MAlice/Ast/BinaryAndOperatorAst.h>
#include <MAlice/Ast/BinaryXorOperatorAst.h>
#include <MAlice/Ast/BinaryOrOperatorAst.h>
#include <MAlice/Ast/AssignmentOperatorAst.h>
#include <MAlice/Ast/UnaryAteOperatorAst.h>
#include <MAlice/Ast/UnaryDrankOperatorAst.h>
#include <MAlice/Ast/UnaryNegOperatorAst.h>
#include <MAlice/Ast/UnaryNotOperatorAst.h>
#include <MAlice/Ast/FloatLiteralAst.h>
#include <MAlice/Ast/ReturnExpressionAst.h>
#include <MAlice/Ast/VariableDeclAst.h>
#include <MAlice/Ast/VariableAst.h>
#include <MAlice/Ast/TypeAst.h>

#include <MAlice/Trace.h>

#include <boost/program_options.hpp>
namespace po = boost::program_options;
using namespace yy;

extern int yy_flex_debug;

char llvm::DwarfWriter::ID = 0;

bool llvm::DwarfWriter::ShouldEmitDwarfDebug() const
{
	return false;
}

static void MAlice_ThrowSEGV(int e)
{
	throw std::runtime_error("Alice tripped, sorry! :-(");
}

location mLocation;

void nothing()
{
	MALICE_TRACE;

}

void mio()
{
	MALICE_TRACE;

	nothing();

	throw runtime_error("mio");
}

void foo()
{
	MALICE_TRACE;

	mio();
}

void bar()
{
	MALICE_TRACE;

	try
	{
		foo();
	}
	catch(exception& e)
	{
		cout << e.what() << endl << endl;
		Trace::getTrace()->dump(cout);
	}
}

int main(int argc, char** argv)
{
	MALICE_TRACE;

	using namespace MAlice;

	// bar();

	signal(SIGSEGV, &::MAlice_ThrowSEGV);

	// Declare the supported options.
	// Using boost library command
	// parse and check the programs options and arguments
	po::command_line_style::style_t pstyle = po::command_line_style::style_t(
		po::command_line_style::unix_style |
		po::command_line_style::case_insensitive |
		po::command_line_style::allow_long_disguise );

	po::options_description desc("Allowed options");
	desc.add_options()
	("help", "produce the help message")
	("F"   , po::value< vector<string> >(), ".alice file name to be compiled")
	("O"   , po::value< int >()->default_value(Codegen::NONE), "Optimisation level (default 0: no optimisation")
	("L"   , po::value< bool >()->default_value(false), "Perform lexical analysis only")
	("P"   , po::value< bool >()->default_value(false), "Perform parsing only, overridden by 'L' flag")
	("D"   , po::value< bool >()->default_value(false), "Dump the generated module to standard output [default is false]")
	;

	po::positional_options_description pdesc; // options
	po::variables_map varmap;                 // arguments

	pdesc.add("F", -1);
	po::store(po::command_line_parser(argc, argv)
		.style(pstyle)
		.options(desc)
		.positional(pdesc)
		.run()
	, varmap);
	po::notify(varmap);
	if (varmap.count("help")) {
		cout << desc << "\n";
		return 0;
	}

	if (varmap.count("F") == 0)
	{
		std::cerr << "Give the name of a script as an argument to "
				  << argv[0]
				  << endl;
		return(EXIT_FAILURE);
	}

	vector<string> files(varmap["F"].as< vector<string> >() );

	// for now just compile one file
	const string fullname = files[0];
	const string filename = fullname.substr(0, fullname.rfind('.'));
	const string::size_type index = filename.rfind('/');
	const string nicename = (index == string::npos)
			? filename
			: filename.substr(index + 1);

	try
	{
		string   e = "";
		Codegen* g = new Codegen();
		Module * module  = g->getModule();

		MAliceDriver driver(nicename);
		MAliceParser parser(driver);

#ifndef NDEBUG
		parser.set_debug_level(10);
		parser.set_debug_stream(cerr);
		yy_flex_debug = 1;
#else
		yy_flex_debug = 0;
#endif

		// Check if lexical analysis is requested by the user	
		if ( varmap["L"].as< bool >() )
		{
			MAliceParser::semantic_type S;
			MAliceParser::location_type L;
			while(char c = yylex(&S, &L))
			{
				cout << c << " | " << (int)c << endl;
			}
			return (EXIT_SUCCESS);
		}

		// redirect the input stream
		if (!(stdin = fopen(fullname.c_str(), "r")))
		{
			cerr << '\'' << fullname << "' does not exists!" << endl;
			return (EXIT_FAILURE);
		}

		// parse the program
		if (parser.parse())
		{
			return (EXIT_FAILURE);
		}
		else if ( varmap["P"].as< bool >() )
		{
			// parse only flag is enabled, don't generate code
			return (EXIT_SUCCESS);
		}

		// generate the program
		cout << "Generating Code..." << endl;
		g->finalise(driver, static_cast<Codegen::Optimisation>(varmap["O"].as<int>()));
		cout << "Generating Code... Done" << endl;

		if (varmap["D"].as< bool >())
		{
			// optimise all of the declared function
			cout << endl << "Module:" << endl << endl;
			module->dump();
		}


		// write the byte code
		const string executable = filename;
		const string bytecode   = filename + ".bal";

		{ // write the compilation script
			ofstream os( executable );
			os << "#!/bin/bash" << endl;
			os << "opt -O3 " << bytecode << " -o " << bytecode;
			os << "lli " << bytecode << endl;
			os << endl;
			os.close();

			// execute the compilation script
			const string chmod = string("chmod a+x ") + executable;
			if ( int e = system( chmod.c_str() ) )
			{
				cerr << "System call '" << chmod << "' failed with exit code: " << e << endl;
			}
		}

		{ // write bytecode to file
			raw_fd_ostream os(bytecode.c_str(), e, raw_fd_ostream::F_Binary);
			WriteBitcodeToFile(module, os);
			os.close();
		}

		return (EXIT_SUCCESS);
	}
	catch (exception& e)
	{
		std::cerr << "Error: " << e.what() << std::endl;

		Trace::getTrace()->dump(cerr);

		return (EXIT_FAILURE);
	}
	catch (...)
	{
		std::cerr << "Unknown Error" << std::endl;

		Trace::getTrace()->dump(cerr);

		return (EXIT_FAILURE);
	}
}
